package avicit.bdp.dms.perm.service;

import avicit.bdp.common.base.BaseService;
import avicit.bdp.common.service.dto.BdpColumnDTO;
import avicit.bdp.common.service.dto.BdpTableDTO;
import avicit.bdp.common.service.dto.DataSourceDTO;
import avicit.bdp.common.service.service.DataSourceService;
import avicit.bdp.common.utils.BdpLogUtil;
import avicit.bdp.common.utils.database.DBUtils;
import avicit.bdp.core.constant.Constants;
import avicit.bdp.core.util.json.JSONUtils;
import avicit.bdp.dms.das.dto.BdpDasCategoryDTO;
import avicit.bdp.dms.das.service.BdpDasCategoryService;
import avicit.bdp.dms.das.service.BdpDasTableService;
import avicit.bdp.dms.perm.dao.DataPermDAO;
import avicit.bdp.dms.perm.dto.DataPermDTO;
import avicit.bdp.dms.perm.utils.enums.ColumnTypeEnum;
import avicit.platform6.api.system.SysRoleClient;
import avicit.platform6.commons.utils.ComUtil;
import avicit.platform6.commons.utils.PojoUtil;
import avicit.platform6.core.exception.BusinessException;
import avicit.platform6.core.properties.PlatformConstant;
import avicit.platform6.core.rest.msg.QueryRespBean;
import com.alibaba.fastjson2.JSONObject;
import com.github.pagehelper.Page;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @金航数码科技有限责任公司
 * @作者：developer
 * @邮箱：developer@avic-digital.com
 * @创建时间： 2022-11-08
 * @类说明：DataPermService
 * @修改记录：
 * @注意事项：
 * @主要功能：行权限管理Service接口
 */
@Service
public class DataPermService extends BaseService<DataPermDAO, DataPermDTO> {
    /**
     * 数据源服务
     */
    @Autowired
    private DataSourceService dataSourceService;

    /**
     * 模型表服务
     */
    @Autowired
    private BdpDasTableService dasTableService;

    /**
     * 模型分类服务
     */
    @Autowired
    private BdpDasCategoryService dasCategoryService;

    /**
     * V6平台系统角色
     */
    @Autowired
    private SysRoleClient sysRoleClient;

    /**
     * 数据库类型转换
     */
    private static Map<ColumnTypeEnum, ColumnTypeEnum> typeMap;
    static {
        typeMap = new HashMap<>();

        typeMap.put(ColumnTypeEnum.TINYIINT, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.FLOAT, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.DECIMAL, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.NUMBER, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.SAMLLINT, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.INT, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.BIGINT, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.DOUBLE, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.DOUBLE, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.MEDIUMINT, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.INT2, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.INT4, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.INT8, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.REAL, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.NUMBERIC, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.BYTEA, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.FLOAT4, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.FLOAT8, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.INTEGER, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.INT32, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.INT64, ColumnTypeEnum.INTEGER);
        typeMap.put(ColumnTypeEnum.CHAR, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.VARCHAR2, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.NCHAR, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.NVARCHAR2, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.VARCHAR, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.BLOB, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.CLOB, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.DATE, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.TIMESTAMP, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.TEXT, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.MEDIUMTEXT, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.LONGTEXT, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.TIME, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.YEAR, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.DATETIME, ColumnTypeEnum.STRING);
        typeMap.put(ColumnTypeEnum.STRING, ColumnTypeEnum.STRING);
    }

    /**
     * 新增数据权限
     * @param dataPermDTO
     */
    public boolean addDataPerm(DataPermDTO dataPermDTO) {
        // step1:合法性检查
        checkAddPermValidity(dataPermDTO);

        // step2:遍历分类节点，如果是非叶子节点，获取子节点，设置权限
        DataPermDTO permDTO = buildDataPerm(dataPermDTO);

        // step3:元数据持久化
        saveDataPerm2Db(permDTO);

        return true;
    }

    /**
     * 根据模型ID删除权限(业务模块删除模型时同步删除权限)
     * @param id
     * @return
     */
    public Integer deleteDataPermById(String id) {
        if (StringUtils.isNotBlank(id)) {
            List<String> idList = Arrays.asList(id.trim().split(","));
            for (String permId : idList) {
                this.mapper.deleteByPrimaryKey(permId);
            }
        }

        String logTitle = "移除数据授权：id为【" + id + "】";
        BdpLogUtil.log("数据授权模块", logTitle, PlatformConstant.OpType.delete);
        return 0;
    }

    /**
     * updateDataPerm
     * @param dataPermDTO
     */
    public boolean updateDataPerm(DataPermDTO dataPermDTO) {
        // step1:合法性检查
        checkUpdatePermValidity(dataPermDTO);

        // step2:遍历分类节点，如果是非叶子节点，获取子节点，设置权限
        if (CollectionUtils.isNotEmpty(dataPermDTO.getColumnList())) {
            dataPermDTO.setColumns(JSONObject.toJSONString(dataPermDTO.getColumnList()));
        }

        // step4:元数据持久化
        DataPermDTO oldDTO = this.selectByPrimaryKey(dataPermDTO.getId());
        updateDataPerm2db(dataPermDTO, oldDTO);

        return true;
    }

    /**
     * 根据主键ID查询权限详情
     * @param id
     * @return
     */
    public DataPermDTO getDataPermDetail(String id) {
        if (StringUtils.isBlank(id)) {
            return null;
        }

        DataPermDTO dataPerm = this.mapper.getDataPermDetail(id);
        if (dataPerm == null) {
            return null;
        }

        if (StringUtils.isNotBlank(dataPerm.getColumns())) {
            dataPerm.setColumnList(JSONUtils.toList(dataPerm.getColumns(), String.class));
        }

        dataPerm.setRoleName(this.sysRoleClient.getSysRoleNameById(dataPerm.getRoleId()));
        dataPerm.setSql(generateSql(dataPerm));

        return dataPerm;
    }

    /**
     * 数据授权时，获取模型分类树
     * 注意：
     *  1、授权界面中，授权角色获取模型分类树(仅包含已授权模型归属节点的信息)
     * @param roleId
     * @param keyWords
     * @return
     */
    public List<BdpDasCategoryDTO> getCategoryTree(String roleId, String keyWords) {
        if (StringUtils.isBlank(roleId)) {
            return new ArrayList<>();
        }

        // 根据角色，获取已授权模型的归属分类节点ID列表
        List<String> nodeIdList = this.mapper.getNodeIdListByRole(roleId);

        // 构建分类树
        return this.dasCategoryService.getCategoryTree(nodeIdList, keyWords);
    }

    /**
     * 授权界面，根据角色ID和分类ID获取权限列表
     * @param roleId
     * @param typeId
     * @param keyWords
     * @param pageNo
     * @param pageSize
     * @return
     */
    public QueryRespBean<DataPermDTO> getAuthedDataList(String roleId, String typeId, String keyWords,
                                                        Integer pageNo, Integer pageSize) {
        QueryRespBean<DataPermDTO> queryRespBean = new QueryRespBean<>();

        if (StringUtils.isBlank(typeId)) {
            return queryRespBean;
        }

        // step1：获取子节点列表（包括自身）
        List<String> typeIdList = getChildNodeIdList(roleId, typeId);

        // step2：分页查询数据权限列表
        com.github.pagehelper.PageHelper.startPage(pageNo, pageSize);
        Page<DataPermDTO> queryRet = this.mapper.queryDataPermByCond(roleId, typeIdList, keyWords);
        queryRespBean.setResult(queryRet);
        return queryRespBean;
    }

    /**
     * 验证SQL语法合法性
     * @param dataPermDTO
     * @return
     */
    public boolean validSql(DataPermDTO dataPermDTO) {
        BdpTableDTO tableDTO = this.dasTableService.queryTableInfoById(dataPermDTO.getTableId());
        if (tableDTO == null) {
            throw new BusinessException("未找到模型表,id=" + dataPermDTO.getTableId());
        }

        DataSourceDTO dataSourceDTO = dataSourceService.queryDataSourceById(tableDTO.getDataSourceId());
        String filterCond = getFilterCondition(dataPermDTO);
        try {
            return DBUtils.checkGrammar(dataSourceDTO, dataPermDTO.getTableName(), filterCond);
        } catch (Exception e) {
            throw new BusinessException("SQL语法检查失败,errMsg = " + e.getMessage());
        }
    }

    /**
     * 生成权限过滤SQL
     * @param dataPermDTO
     * @return
     */
    public String generateSql(DataPermDTO dataPermDTO) {
        BdpTableDTO tableDTO = this.dasTableService.queryTableInfoById(dataPermDTO.getTableId());
        if (tableDTO == null) {
            throw new BusinessException("未找到模型表,id=" + dataPermDTO.getTableId());
        }

        return buildQuerySql(tableDTO, dataPermDTO);
    }

    /**
     * 列举指定表中所有列信息
     * @param tableId
     * @return
     */
    public List<String> listColumns(String tableId) {
        if (StringUtils.isEmpty(tableId)) {
            throw new BusinessException("获取模型表列信息失败，tableId为null.");
        }

        // step1:根据tableId获取datasourceId等信息
        List<BdpColumnDTO> bdpColumnDTOS = queryColumnInfoById(tableId);

        // step2:获取模型表列信息
        List<String> colNames = new ArrayList<>();
        for (BdpColumnDTO columnDTO : bdpColumnDTOS) {
            colNames.add(columnDTO.getName());
        }

        return colNames;
    }

    /**
     * 数据授权时，获取模型分类树及模型
     * 注意：
     *  1、授权界面中，数据授权时获取未导入的模型分类树及模型（根据角色和已授权模型筛选）
     * @param roleId
     * @return
     */
    public List<BdpDasCategoryDTO> findCategoryAndTableList(String roleId) {
        if (StringUtils.isBlank(roleId)) {
            return new ArrayList<>();
        }

        // ste:1：根据角色，获取已授权模型ID列表
        List<String> tableIdList = this.mapper.getTableIdListByRole(roleId);

        // step2：构建分类树及模型
        return this.dasCategoryService.findUnauthedTableList(roleId, tableIdList);
    }

    /**
     * 新增权限时合法性检查
     * @param dataPermDTO
     */
    private void checkAddPermValidity(DataPermDTO dataPermDTO) {
        if (dataPermDTO == null) {
            throw new BusinessException("入参为null.");
        }
        if (StringUtils.isBlank(dataPermDTO.getRoleId())) {
            throw new BusinessException("未设置角色.");
        }
        if (StringUtils.isBlank(dataPermDTO.getTableId())) {
            throw new BusinessException("未设置模型.");
        }
    }

    /**
     * 修改权限时合法性检查
     * @param dataPermDTO
     */
    private void checkUpdatePermValidity(DataPermDTO dataPermDTO) {
        if (dataPermDTO == null) {
            throw new BusinessException("入参为null.");
        }
        if (StringUtils.isBlank(dataPermDTO.getRoleId())) {
            throw new BusinessException("角色为空.");
        }
        if (StringUtils.isBlank(dataPermDTO.getTypeId())) {
            throw new BusinessException("分类ID为空.");
        }
        if (StringUtils.isBlank(dataPermDTO.getTableId())) {
            throw new BusinessException("模型ID为空.");
        }
        if (StringUtils.isBlank(dataPermDTO.getTableName())) {
            throw new BusinessException("模型名称为空.");
        }
    }

    /**
     * 构建权限
     * @param dataPermDTO
     * @return
     */
    private DataPermDTO buildDataPerm(DataPermDTO dataPermDTO) {
        // 根据tableId获取模型信息
        BdpTableDTO tableDTO = this.dasTableService.queryTableInfoById(dataPermDTO.getTableId());

        // 构建权限
        DataPermDTO newDto = new DataPermDTO();
        newDto.setId(ComUtil.getId());
        newDto.setName(tableDTO.getName());
        newDto.setRoleId(dataPermDTO.getRoleId());
        newDto.setTypeId(tableDTO.getTypeId());
        newDto.setTableId(dataPermDTO.getTableId());
        newDto.setTableName(tableDTO.getName());
        newDto.setTableCnName(tableDTO.getCnName());
        newDto.setDatasourceType(tableDTO.getDataSourceType());
        newDto.setAction(dataPermDTO.getAction());
        newDto.setEffect(Constants.EFFECT_ALLOW);
        newDto.setDimension(Constants.DIMENSION_ROLE);
        newDto.setRelated(Constants.NOT_RELATED_PERM);

        if (CollectionUtils.isNotEmpty(dataPermDTO.getColumnList())) {
            newDto.setColumns(JSONObject.toJSONString(dataPermDTO.getColumnList()));
        }

        newDto.setCustomWhereCond(dataPermDTO.getCustomWhereCond());
        PojoUtil.setSysProperties(newDto, PlatformConstant.OpType.insert);

        return newDto;
    }

    /**
     * 持久化到内存
     * @param dataPermDTO
     */
    private void saveDataPerm2Db(DataPermDTO dataPermDTO) {
        if (dataPermDTO != null) {
            // 持久化
            this.insert(dataPermDTO);

            // 审计日志
            BdpLogUtil.log4Insert(dataPermDTO);
        }
    }

    /**
     * 持久化到内存
     * @param newDto
     * @param oldDto
     */
    private void updateDataPerm2db(DataPermDTO newDto, DataPermDTO oldDto) {
        if (oldDto != null && newDto != null) {
            // 持久化
            this.updateByPrimaryKeySelective(newDto);

            // 审计日志
            BdpLogUtil.log4Update(newDto, oldDto);
        }
    }

    /**
     * 获取过滤条件
     * @param dataPermDTO
     * @return
     */
    private String getFilterCondition(DataPermDTO dataPermDTO) {
        String filterCond = " ";

        // 过滤条件（自定义SQL）
        if (StringUtils.isNotBlank(dataPermDTO.getCustomWhereCond())) {
            filterCond = dataPermDTO.getCustomWhereCond();
        } else {
            // DBUtils.checkGrammar中语法格式固定为"select * from " + tableName + " where " + filterCondition + " limit 1"
            // 如果未配置filterCond，语法检查会失败;
            filterCond = "1=1";
        }

        return filterCond;
    }

    /**
     * 根据行权限构建查询SQL
     * @param bdpTableDTO
     * @param dataPermDTO
     * @return
     */
    private String buildQuerySql(BdpTableDTO bdpTableDTO, DataPermDTO dataPermDTO) {
        StringBuilder sql = new StringBuilder();

        // 受控字段
        List<String> columnList = dataPermDTO.getColumnList();
        if (CollectionUtils.isNotEmpty(columnList)) {
            sql.append("select ").append(String.join(",", columnList)).append(" from " + bdpTableDTO.getName());
        } else {
            sql.append("select * from " + bdpTableDTO.getName());
        }

        // where自定义SQL
        if (org.apache.commons.lang3.StringUtils.isNotBlank(dataPermDTO.getCustomWhereCond())) {
            sql.append(" where " + dataPermDTO.getCustomWhereCond());
        }

        return sql.toString();
    }

    /**
     * 根据tableId获取模型表列信息
     * @param tableId
     * @return
     */
    private List<BdpColumnDTO> queryColumnInfoById(String tableId) {
        return this.dasTableService.queryColumnInfoById(tableId);
    }

    /**
     * 获取子节点ID列表（包括自身）
     * @param roleId
     * @param nodeId
     * @return
     */
    private List<String> getChildNodeIdList(String roleId, String nodeId) {
        // 获取指定角色分类树
        List<BdpDasCategoryDTO> allNodeList = getCategoryTree(roleId, null);

        // 获取指定节点
        List<BdpDasCategoryDTO> resultNodeList = Lists.newArrayList();
        recursiveGetNodeById(nodeId, allNodeList, resultNodeList);
        if (CollectionUtils.isEmpty(resultNodeList)) {
            return Lists.newArrayList();
        }

        // 遍历获取子节点（包括自身）
        List<String> childNodeIdList = Lists.newArrayList();
        recursiveGetChildNodeIdList(resultNodeList.get(0), childNodeIdList);

        return childNodeIdList;
    }

    /**
     * 递归获取指定节点
     * @param nodeId
     * @param allNodeList
     * @param resultNodeList
     * @return
     */
    private void recursiveGetNodeById(String nodeId, List<BdpDasCategoryDTO> allNodeList,
                                      List<BdpDasCategoryDTO> resultNodeList) {
        if (CollectionUtils.isEmpty(allNodeList)) {
            return;
        }

        for (BdpDasCategoryDTO dto : allNodeList) {
            if (dto == null || StringUtils.isBlank(dto.getId())) {
                continue;
            }
            if (dto.getId().equals(nodeId)) {
                resultNodeList.add(dto);
                return;
            }
            if (dto.getLeaf() == 0) {
                recursiveGetNodeById(nodeId, dto.getChildren(), resultNodeList);
            }
        }
    }

    /**
     * 递归获取所有子节点ID列表
     * @param parentNode
     * @param resultNodeIdList
     */
    private void recursiveGetChildNodeIdList(BdpDasCategoryDTO parentNode, List<String> resultNodeIdList) {
        if (parentNode == null) {
            return;
        }

        resultNodeIdList.add(parentNode.getId());

        if (CollectionUtils.isEmpty(parentNode.getChildren())) {
            return;
        }

        for (BdpDasCategoryDTO currNode : parentNode.getChildren()) {
            recursiveGetChildNodeIdList(currNode, resultNodeIdList);
        }
    }
}
